#Copyright Okhtay Shoghi 2008

#require 'external/mysql' 
require 'rubygems'
require 'mysql'
require 'uranium/loggerwrapper'
require 'uranium/constants'
require 'uranium/hash'
require 'uranium/utils'
require 'cgi'

MODE_ALL			= 'all'
MODE_ALL_FAILS      = 'allFails'
MODE_NOT_RUN		= 'allNotRun'
MODE_CASES			= 'specificCases'
MODE_SUITES			= 'specificSuites'
MODE_LIST_SUITES    = 'listSuites'
MODE_NEW			= 'new'

class Database
	@@db	= nil
	@@log	= nil

	def self.init
		Log.dbdebug "Initializing connection to database...", "mysql"
	
		@@db = Mysql.real_connect(DB_SERVER_NAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE, nil, nil, Mysql::CLIENT_TRANSACTIONS)
	end

	def self.get_row_from_query(str, field=nil)
		result = self::query(str)
		row	   = result.fetch_hash
		
		return row[field] if row && field
		return row
	end

	def self.query_into_array(str, fields=nil)
		result	= self::query(str)
		rows	= Array.new
		
		while row = result.fetch_hash do
			if (fields)
				if fields.class == String
					rows.push(row[fields])
				else
					r = {}
					fields.each do |f|
						r[f] = row[f]
					end
					rows.push(r)
				end
			else
				rows.push(row)
			end
		end
		
		return rows
	end

	def self.close
		@@db.close
		@@db = nil
	end

	def self.query(str)	
		Log.dbdebug str
		
		begin
			self::init if (@@db == nil or !@@db.ping)
			result = @@db.query(str)
		rescue Exception => e
			@@db.close if @@db
			self::init
			Log.debug e.inspect
			Log.debug e.backtrace.inspect
		end
		
		return result
	end
	
	def self.insert_id
		return @@db.insert_id
	end
	
	def self.escape_string(str)
		return @@db.escape_string(str)
	end
end

class UraniumQuery
	@@total = 0

	def self.get_suites_in_run(runId)
		query = "SELECT suiteName
				FROM SuiteResults
				WHERE runId=#{runId}
				AND state='W'
				ORDER BY suiteName ASC";
			
		return Database.query_into_array(query, 'suiteName')
	end

	def self.omit_suite(suiteName, runId)
		query = "UPDATE SuiteResults
				SET state='O'
				WHERE suiteName='#{suiteName}'
				AND runId=#{runId}"

		Database.query(query)
	end
  
	def self.get_run_version(runId)
		return Database.get_row_from_query("SELECT version FROM TestRuns WHERE id=#{runId}","version")
	end

	def self.get_waiting_cases(runId)
		query = "SELECT name, suiteName
				FROM TestCaseResults
				WHERE runId=#{runId}
				AND state='W'
				ORDER BY suiteName ASC, name ASC"
      
		return Database.query_into_array(query)
	end
  
	def self.insert_tests_in_run(runId)
		suites = self.get_suites_in_run(runId)
		puts suites.inspect
		tests = Array.new

		suites.each do |suite|
			list = Array.new
			begin
				eval("list = #{suite}.public_instance_methods.delete_if{|x|!x.match(/^test/)}")
				list.each do |test|
					tests.push("('#{test}','#{suite}',#{runId},'W')")
				end
			rescue Exception => e
				#suite not found or error with class
			end
		end

		query = "INSERT INTO TestCaseResults(name, suiteName, runId, state) 
				VALUES #{tests.join(',')}
				ON DUPLICATE KEY UPDATE
				state='W',numberTimesRan=0"
		Database.query(query)
	end

	def self.sync_totals(runId)
		query =	
			"UPDATE TestRuns 
			 	  SET totals='#{toJSON(self.get_totals(runId))}' 
		  	WHERE id=#{runId}"
		
		Database.query(query)
	end

	def self.start_run(runId, mode=MODE_ALL, opts={})
		return if runId == 'test'
	  
		runQuery   = "UPDATE TestRuns SET state='R' WHERE id=#{runId}"
		suiteQuery = "UPDATE SuiteResults SET state='W' WHERE runId=#{runId} "
		caseQuery  = "UPDATE TestCaseResults SET state='W' WHERE runId=#{runId} "
		
		case mode
			when MODE_ALL
				version	   = Callbacks.get_version
				runQuery   = "UPDATE TestRuns SET state='R',startTime=NOW(),version='#{version}' WHERE id=#{runId}"
			when MODE_ALL_FAILS
				caseQuery  += "AND State='F'"
				suiteQuery += "AND State='F'"
			when MODE_SUITES
				suites = opts['suite_ids'].collect{|s| s="'#{s}'"}
				suites = suites.join(',')

				suiteQuery += "AND suiteName in (#{suites})"
				caseQuery  += "AND suiteName in (#{suites})"
			when MODE_CASES
				puts opts.inspect
				suiteQuery += "AND suiteName='#{opts['suiteIds']}'"
			
				case_array = []
				opts['case_ids'].each do |cs|
					tc,ts = cs.split('|')
					case_array.push("(name='#{tc}' AND suiteName='#{ts}')")
				end
				caseQuery  += case_array.join(" OR ")
			when MODE_NOT_RUN
				caseQuery  += "AND State='S'"
				suiteQuery += "AND State='S'"
		end
		  
		Database.query(runQuery)    
		Database.query(suiteQuery)
		Database.query(caseQuery)
		
		self.insert_tests_in_run(runId) if mode==MODE_ALL
		self.sync_totals(runId)
		
		return self.get_suites_in_run(runId)
	end
	
	def self.get_totals(runId)
		get_totals_query = "SELECT S.id,
							(SELECT COUNT(*) 
							FROM TestCaseResults 
							WHERE runId=#{runId} 
							AND state=S.id) AS count
							FROM States S"
	
		totals = Hash.new
		result = Database.query(get_totals_query)
		while row = result.fetch_hash do
			totals[row['id']] = row['count'].to_i
		end

		get_total_all = "SELECT COUNT(*) AS 'all' FROM TestCaseResults WHERE runId=#{runId}"
		totals['all'] = Database.get_row_from_query(get_total_all)['all']
		return totals
	end
  
	def self.finalize_run(run_id, mode=nil)
		if (mode==MODE_ALL_FAILS)      
			Database.query("UPDATE TestCaseResults SET state='F' WHERE state in ('W','R')")
			Database.query("UPDATE SuiteResults SET state='F' WHERE state in ('W','R')")
		else  
			query = "UPDATE TestCaseResults
					SET state='S'
					WHERE state in ('W','R')"

			Database.query(query)
      
			query = "UPDATE SuiteResults
					SET state='S'
					WHERE state in ('W','R')"
      
			Database.query(query)
		end
    
		Database.query("UPDATE TestRuns SET state='C' WHERE id=#{run_id}")
		self.sync_totals(run_id)
		Database.close
	end
	
	def self.sync_suite_state(suiteName, runId)
		query = "SELECT state 
				FROM TestCaseResults 
				WHERE runId=#{runId}
				AND suiteName='#{suiteName}'
				GROUP BY state 
				ORDER BY FIELD(state, 'R','F','D','O','E','P')
				LIMIT 1"
		
		state = Database.get_row_from_query(query, 'state')
		state = 'P' if state == 'E'
		
		query = "UPDATE SuiteResults
				SET state='#{state}'
				WHERE runId=#{runId}
				AND suiteName='#{suiteName}'"
		
		Database.query(query)
	end

	def self.get_case_state(runId, suiteName, testName)
		return Database.get_row_from_query("SELECT state FROM TestCaseResults WHERE runId=#{runId} AND suiteName='#{suiteName}' AND name='#{testName}'", 'state')
	end
	
	def self.set_case_state(runId, suiteName, testName, state, msg='', appendMsg=true)
		locator = "runId=#{runId} AND suiteName='#{suiteName}' AND name='#{testName}'"

		query = "INSERT IGNORE INTO TestCaseResults(name, suiteName, runId, defects) VALUES('#{testName}','#{suiteName}','#{runId}','')"
		Database.query(query)

		case(state)
			when 'R':
				query = "UPDATE TestCaseResults SET state='#{state}', startTime=NOW() WHERE #{locator}"
			when 'F':
				msg = "<div class=\"entry\">#{CGI.escapeHTML(msg.gsub("\n","<br />"))}<div class=\"timestamp\">#{Time.new.strftime("%d/%m/%y %I:%M %p")}</div></div>"
				if (appendMsg)
					query	= "UPDATE TestCaseResults SET state='#{state}', message=CONCAT('#{msg}',IFNULL(message,'')) WHERE #{locator}"
				else
					query	= "UPDATE TestCaseResults SET state='#{state}', message='#{msg}' WHERE #{locator}"
				end
			else
				query = "UPDATE TestCaseResults SET state='#{state}' WHERE #{locator}"
		end
		
		Database.query(query)
		self.scan_defects(runId, suiteName, testName) if state=='F'
	end

	def self.scan_defects(runId, suiteName, testName)
		query	= "SELECT defects, message FROM TestCaseResults WHERE runId < #{runId} AND state='D' AND name='#{testName}' AND suiteName='#{suiteName}' ORDER BY runId DESC LIMIT 1"
		result	= Database.get_row_from_query(query)

		return unless result

		regex = /<div class="entry">(.*?)<div/m
		last_error = result.message.match(regex).to_a[1]
		new_error = Database.get_row_from_query("SELECT message FROM TestCaseResults WHERE runId=#{runId} and name='#{testName}' AND suiteName='#{suiteName}'").message.match(regex).to_a[1]

		last_error.gsub!(/:\d+:in/, 'in')
		new_error.gsub!(/:\d+:in/, 'in')

		if (last_error == new_error)
			query = "UPDATE TestCaseResults SET defects='#{result.defects}', state='D' WHERE runId=#{runId} AND name='#{testName}' AND suiteName='#{suiteName}'"
			Database.query(query)
			
			query = "INSERT INTO Defects(runId,version,testName,defectId) VALUES(#{runId},(SELECT version FROM TestRuns WHERE id=#{runId}),'#{testName}','#{result.defects}')"
			Database.query(query)
		end
	end

	def self.increment_case_ran_count(runId, suiteName, testName)
		query = "UPDATE TestCaseResults 
				SET numberTimesRan=numberTimesRan+1 
				WHERE runId=#{runId}
				AND suiteName='#{suiteName}' 
				AND name='#{testName}'"
	
		Database.query(query)	
	end

	def self.set_suite_state(runId, suiteName, state)
		return if runId == 'test'
		Database.query("UPDATE SuiteResults SET state='#{state}' WHERE runId=#{runId} AND suiteName='#{suiteName}'")
	end
end

class RunDetails
	@@data = nil

	def self.get(key)
		return @@data == nil ? nil : @@data[key]
	end

	def self.method_missing(method, *args)
		return self.get(method.to_s)
	end

	def self._BrowserType
		# bmap = {'firefox' => '*chrome', 'ie' => '*iehta', 'safari' => '*safari'}
		# browser = self.Browser.downcase.match(/(ie|firefox|safari|opera)/).to_a[0]
		return self.Browser
	end

	def self.Locale
		return (super ? super : 'en')
	end

	def self.Browser
		return (super ? super : '*firefox')
	end

	def self.convert_user_data_to_hash(ud_str)
		data = fromJSON(ud_str)
		lookup = {}
		data.each do |val|
			lookup[val['field']] = val['value']
		end
		return lookup
	end

	def self.convert_hash_to_user_data(hash)
		data = []
		hash.each do |key,value|
			data.push({"field" => key, "value" => value})
		end
		return data.to_json.gsub('\'','\\\'')
	end

	def self.set_fields(values, runId)
		data = Database.get_row_from_query("SELECT userData FROM TestRuns WHERE id=#{runId}", "userData")
		data = self.convert_user_data_to_hash(data)

		values.each do |key,value|
			data[key] = value
		end
		data = self.convert_hash_to_user_data(data)

		Database.query("UPDATE TestRuns SET userData = '#{data}' WHERE id=#{runId}")
	end

	def self.init_with_json_file(path)
		Log.debug "Init run details from file: #{path}"
		@@data = fromJson(File.read(path))
	end

	def self.init(runId)
		@@data = Hash.new

		if (runId != 'test')
			query = "SELECT userData FROM TestRuns WHERE id=#{runId}"

			data = Database.get_row_from_query(query)['userData']
			data = fromJson(data)

			if (data)
				data.each do |entry|
					@@data[entry['field']] = entry['value']
				end
			end
		end
	end
end
